#!/usr/bin/env python3
"""Calculate one's GPA from a percentage scale to a 4 point scale using various
approaches to see which lends the highest GPA."""

import decimal

d = decimal.Decimal

percent_to_letter_scale = [  # http://www.calendar.ubc.ca/vancouver/index.cfm?tree=3,42,96,0
        [d('90'), 'A+'],
        [d('85'), 'A'],
        [d('80'), 'A-'],
        [d('76'), 'B+'],
        [d('72'), 'B'],
        [d('68'), 'B-'],
        [d('64'), 'C+'],
        [d('60'), 'C'],
        [d('55'), 'C-'],
        [d('50'), 'D'],
        [d('0'), 'F'],
    ]

percent_to_points_scale = {  # http://www.arts.ubc.ca/students/academic-planning-advising/advising/academicperformance/gpa-equivalency.html
        d('100'): d('4'),  # 4.33
        d('99'): d('4'),
        d('98'): d('4'),
        d('97'): d('4'),
        d('96'): d('4'),
        d('95'): d('4'),
        d('94'): d('4'),
        d('93'): d('4'),
        d('92'): d('4'),
        d('91'): d('4'),
        d('90'): d('4'),
        d('89'): d('4'),  # 4.3
        d('88'): d('4'),  # 4.2
        d('87'): d('4'),  # 4.1
        d('86'): d('4'),
        d('85'): d('3.95'),
        d('84'): d('3.9'),
        d('83'): d('3.85'),
        d('82'): d('3.8'),
        d('81'): d('3.75'),
        d('80'): d('3.7'),
        d('79'): d('3.6'),
        d('78'): d('3.5'),
        d('77'): d('3.4'),
        d('76'): d('3.3'),
        d('75'): d('3.2'),
        d('74'): d('3.1'),
        d('73'): d('3'),
        d('72'): d('2.95'),
        d('71'): d('2.9'),
        d('70'): d('2.8'),
        d('69'): d('2.7'),
        d('68'): d('2.65'),
        d('67'): d('2.6'),
        d('66'): d('2.55'),
        d('65'): d('2.5'),
        d('64'): d('2.4'),
        d('63'): d('2.3'),
        d('62'): d('2.2'),
        d('61'): d('2.1'),
        d('60'): d('2'),
        d('59'): d('1.9'),
        d('58'): d('1.8'),
        d('57'): d('1.7'),
        d('56'): d('1.6'),
        d('55'): d('1.5'),
        d('54'): d('1.4'),
        d('53'): d('1.3'),
        d('52'): d('1.2'),
        d('51'): d('1.1'),
        d('50'): d('1'),
    }

letter_to_points_scale = {
        'A+': d('4'),  # 4.3
        'A': d('4'),
        'A-': d('3.7'),
        'B+': d('3.3'),
        'B': d('3'),
        'B-': d('2.7'),
        'C+': d('2.3'),
        'C': d('2'),
        'C-': d('1.7'),
        'D': d('1'),
        'F': d('0'),
    }


def average(grades):
    grade_total = d('0')
    unit_total = d('0')
    for grade, units in grades:
        grade_total += grade * units
        unit_total += units
    return grade_total / unit_total


def percent_to_letter(percent):
    for bottom, letter in percent_to_letter_scale:
        if percent >= bottom:
            break
    else:
        raise ValueError("{} not found in grade scale".format(percent))
    return letter


def percent_to_letter_to_points(grades):
    """Convert percentage to its letter grade and then to points before
    calculating GPA."""
    return round(average([letter_to_points_scale[percent_to_letter(percent)], units]
                    for percent, units in grades),
                 2)


def percent_to_points(grades):
    """Convert percentage to points and then calculate GPA."""
    return round(average([percent_to_points_scale.get(grade, d('0')), units]
                        for grade, units in grades),
                    2)


def percent_gpa_to_points(grades):
    """Calculate the percentage GPA and then convert to a point scale."""
    percent_gpa = round(average(grades))
    return percent_to_points_scale[percent_gpa]


def percent_gpa_to_letter_to_points(grades):
    """Normalize a percentage GPA to a letter grade and then convert to a point
    value."""
    percent_gpa = round(average(grades))
    letter = percent_to_letter(percent_gpa)
    return letter_to_points_scale[letter]


if __name__ == '__main__':
    import csv
    import sys

    file_path = sys.argv[1]
    grades = []
    major_grades = []
    honour_grades = []
    with open(file_path, 'r') as file:
        reader = csv.reader(file)
        for course, grade, units in (line for line in reader if line):
            if course.startswith('#'):
                continue
            values = [d(grade.strip()), d(units.strip())]
            grades.append(values)
            if '*' in course:
                major_grades.append(values)
            if '+' in course:
                honour_grades.append(values)

    calculations = [average, percent_gpa_to_points,
                    #percent_gpa_to_letter_to_points,
                    percent_to_points,
                    percent_to_letter_to_points
                   ]
    print("\nOverall:")
    for calc in calculations:
        result = calc(grades)
        print("  {} = {}".format(calc.__name__.replace('_', ' '), result))
    if major_grades:
        print("\nMajor:")
        for calc in calculations:
            result = calc(major_grades)
            print("  {} = {}".format(calc.__name__.replace('_', ' '), result))
    if honour_grades:
        honours_gpa = average(honour_grades)
        print("\nHonours? {} ({})".format(honours_gpa >= 80, honours_gpa))
